home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-games-data / glchess / gtkui / chessview.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  33.5 KB  |  692 lines

  1. # -*- coding: utf-8 -*-
  2. import sys
  3. from gettext import gettext as _
  4. import traceback
  5. import gobject
  6. import gtk
  7.  
  8. import gtkui
  9. import glchess.ui
  10. import glchess.chess
  11. import glchess.game
  12.  
  13. # Optionally use OpenGL support
  14. openGLErrors = []
  15. haveGLDepthSupport = True
  16. haveGLAccumSupport = True
  17. try:
  18.     import OpenGL.GL
  19. except:
  20.     # Translators: Error message displayed when 3D mode is not available due to no Python OpenGL libraries
  21.     openGLErrors.append(_('No Python OpenGL support'))
  22. try:
  23.     import gtk.gtkgl
  24.     import gtk.gdkgl
  25. except:
  26.     # Translators: Error message displayed when 3D mode is not available due to no Python GTKGLExt libraries
  27.     openGLErrors.append(_('No Python GTKGLExt support'))
  28. else:
  29.     display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE | gtk.gdkgl.MODE_ACCUM)
  30.     try:
  31.         glConfig = gtk.gdkgl.Config(mode = display_mode)
  32.     except gtk.gdkgl.NoMatches:
  33.         display_mode &= ~gtk.gdkgl.MODE_DOUBLE
  34.         display_mode &= ~gtk.gdkgl.MODE_ACCUM
  35.         haveGLAccumSupport = False
  36.         haveGLDepthSupport = False
  37.         try:
  38.             glConfig = gtk.gdkgl.Config(mode = display_mode)
  39.         except gtk.gdkgl.NoMatches:
  40.             # Translators: Error message displayed when 3D mode is not available due to their 3D drivers not being able to provide a suitable display mode
  41.             openGLErrors.append(_('OpenGL libraries do not support required display mode'))
  42. haveGLSupport = len(openGLErrors) == 0
  43.  
  44. __all__ = ['GtkView']
  45.  
  46. class GtkViewArea(gtk.DrawingArea):
  47.     """Custom widget to render an OpenGL scene"""
  48.     # The view this widget is rendering
  49.     view = None
  50.  
  51.     # Pixmaps to use for double buffering
  52.     pixmap = None
  53.     dynamicPixmap = None
  54.     
  55.     # Flag to show if this scene is to be rendered using OpenGL
  56.     renderGL = False
  57.     
  58.     # TODO...
  59.     __glDrawable = None
  60.     
  61.     def __init__(self, view):
  62.         """
  63.         """
  64.         gtk.DrawingArea.__init__(self)
  65.         
  66.         self.view = view
  67.  
  68.         # Allow notification of button presses
  69.         self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_MOTION_MASK)
  70.         
  71.         # Make openGL drawable
  72.         if haveGLSupport:
  73.             gtk.gtkgl.widget_set_gl_capability(self, glConfig)# FIXME:, share_list=glContext)
  74.  
  75.         # Connect signals
  76.         self.connect('realize', self.__init)
  77.         self.connect('configure_event', self.__configure)
  78.         self.connect('expose_event', self.__expose)
  79.         self.connect('button_press_event', self.__button_press)
  80.         self.connect('button_release_event', self.__button_release)
  81.         
  82.     # Public methods
  83.         
  84.     def redraw(self):
  85.         """Request this widget is redrawn"""
  86.         # If the window is visible prepare it for redrawing
  87.         area = gtk.gdk.Rectangle(0, 0, self.allocation.width, self.allocation.height)
  88.         if self.window is not None:
  89.             self.window.invalidate_rect(area, False)
  90.  
  91.     def setRenderGL(self, renderGL):
  92.         """Enable OpenGL rendering"""
  93.         if not haveGLSupport:
  94.             renderGL = False
  95.         
  96.         if self.renderGL == renderGL:
  97.             return
  98.         self.renderGL = renderGL
  99.         self.redraw()
  100.     
  101.     # Private methods
  102.  
  103.     def __startGL(self):
  104.         """Get the OpenGL context"""
  105.         if not self.renderGL:
  106.             return
  107.  
  108.         assert(self.__glDrawable is None)
  109.         
  110.         # Obtain a reference to the OpenGL drawable
  111.         # and rendering context.
  112.         glDrawable = gtk.gtkgl.widget_get_gl_drawable(self)
  113.         glContext = gtk.gtkgl.widget_get_gl_context(self)
  114.  
  115.         # Check were able to get context
  116.         if glDrawable is None or glContext is None:
  117.             return
  118.  
  119.         # OpenGL begin (can fail)
  120.         if not glDrawable.gl_begin(glContext):
  121.             return
  122.         
  123.         self.__glDrawable = glDrawable
  124.  
  125.         if not self.view.ui.openGLInfoPrinted:
  126.             vendor     = OpenGL.GL.glGetString(OpenGL.GL.GL_VENDOR)
  127.             renderer   = OpenGL.GL.glGetString(OpenGL.GL.GL_RENDERER)
  128.             version    = OpenGL.GL.glGetString(OpenGL.GL.GL_VERSION)
  129.             extensions = OpenGL.GL.glGetString(OpenGL.GL.GL_EXTENSIONS)
  130.             print 'Using OpenGL:'
  131.             print 'VENDOR=%s' % vendor
  132.             print 'RENDERER=%s' % renderer
  133.             print 'VERSION=%s' % version
  134.             print 'EXTENSIONS=%s' % extensions
  135.             self.view.ui.openGLInfoPrinted = True
  136.         
  137.     def __endGL(self):
  138.         """Free the OpenGL context"""
  139.         if self.__glDrawable is None or not self.renderGL:
  140.             return
  141.         self.__glDrawable.gl_end()
  142.         self.__glDrawable = None
  143.         
  144.     def __init(self, widget):
  145.         """Gtk+ signal"""
  146.         if self.view.feedback is not None:
  147.             self.view.feedback.reshape(widget.allocation.width, widget.allocation.height)
  148.         
  149.     def __configure(self, widget, event):
  150.         """Gtk+ signal"""
  151.         self.pixmap = gtk.gdk.Pixmap(widget.window, event.width, event.height)
  152.         self.dynamicPixmap = gtk.gdk.Pixmap(widget.window, event.width, event.height)
  153.         self.__startGL()
  154.         if self.view.feedback is not None:
  155.             self.view.feedback.reshape(event.width, event.height)
  156.         self.__endGL()
  157.  
  158.     def __expose(self, widget, event):
  159.         """Gtk+ signal"""
  160.         if self.renderGL:
  161.             self.__startGL()
  162.             if self.__glDrawable is None:
  163.                 return
  164.  
  165.             # Get the scene rendered
  166.             try:
  167.                 if self.view.feedback is not None:
  168.                     self.view.feedback.renderGL()
  169.             except OpenGL.GL.GLerror, e:
  170.                 print 'Rendering Error: ' + str(e)
  171.                 traceback.print_exc(file = sys.stdout)
  172.  
  173.             # Paint this
  174.             if self.__glDrawable.is_double_buffered():
  175.                 self.__glDrawable.swap_buffers()
  176.             else:
  177.                 OpenGL.GL.glFlush()
  178.  
  179.             self.__endGL()
  180.             
  181.         else:
  182.             context = self.pixmap.cairo_create()
  183.             if self.view.feedback is not None:
  184.                 self.view.feedback.renderCairoStatic(context)
  185.             
  186.             # Copy the background to render the dynamic elements on top
  187.             self.dynamicPixmap.draw_drawable(widget.get_style().white_gc, self.pixmap, 0, 0, 0, 0, -1, -1)
  188.             context = self.dynamicPixmap.cairo_create()
  189.         
  190.             # Set a clip region for the expose event
  191.             context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
  192.             context.clip()
  193.            
  194.             # Render the dynamic elements
  195.             if self.view.feedback is not None:
  196.                 self.view.feedback.renderCairoDynamic(context)
  197.                 
  198.             # Draw the window
  199.             widget.window.draw_drawable(widget.get_style().white_gc, self.dynamicPixmap,
  200.                                         event.area.x, event.area.y,
  201.                                         event.area.x, event.area.y, event.area.width, event.area.height)
  202.  
  203.     def __button_press(self, widget, event):
  204.         """Gtk+ signal"""
  205.         self.__startGL()
  206.         if self.view.feedback is not None:
  207.             self.view.feedback.select(event.x, event.y)
  208.         self.__endGL()
  209.         
  210.     def __button_release(self, widget, event):
  211.         """Gtk+ signal"""
  212.         self.__startGL()
  213.         if self.view.feedback is not None:
  214.             self.view.feedback.deselect(event.x, event.y)
  215.         self.__endGL()
  216.  
  217. class GtkView(glchess.ui.ViewController):
  218.     """
  219.     """
  220.     def __init__(self, ui, feedback, moveFormat = 'human', showComments = False):
  221.         """Constructor for a view.
  222.         
  223.         'feedback' is the feedback object for this view (extends ui.ViewFeedback).
  224.         'moveFormat' is the format name to display moves in (string).
  225.         """
  226.         self.ui = ui
  227.         self.feedback = feedback
  228.         self.moveFormat = moveFormat
  229.         self.showComments = showComments
  230.         self.editingComment = False
  231.         self.hasFile = False
  232.         self.selectedMove = -1
  233.         self.requireAttention = False
  234.         self.gameResult = None
  235.         self.whiteTime = None
  236.         self.blackTime = None
  237.         self.title = ''
  238.         self.needsSaving = False
  239.         
  240.         # The GTK+ elements
  241.         self.gui = gtkui.loadGladeFile('chess_view.glade', 'chess_view')
  242.         self.gui.signal_autoconnect(self)
  243.         self.widget = self.gui.get_widget('chess_view')
  244.  
  245.         self.viewWidget = GtkViewArea(self)
  246.         self.gui.get_widget('view_container').add(self.viewWidget)
  247.  
  248.         self.ui.setTooltipStyle(self.gui.get_widget('info_panel'))
  249.  
  250.         # Make a model for navigation (move object, number, description) 
  251.         model = gtk.ListStore(gobject.TYPE_PYOBJECT, int, str)
  252.         iter = model.append()
  253.         # Translators: Move History Combo: Go to the start of the game
  254.         model.set(iter, 0, None, 1, 0, 2, _('Game Start'))
  255.         self.moveModel = model
  256.  
  257.         # Tabs are enabled to make editing the UI easier
  258.         self.gui.get_widget('comment_notebook').set_show_tabs(False)
  259.         
  260.         self.updateInfoPanel()
  261.  
  262.         self.widget.show()
  263.         self.viewWidget.show_all()
  264.         
  265.         self.ui.updateTitle()
  266.  
  267.     def updateInfoPanel(self):
  268.         """
  269.         """
  270.         showPanel = False
  271.         
  272.         panel = self.gui.get_widget('info_panel')
  273.         titleLabel = self.gui.get_widget('panel_title_label')
  274.         descriptionLabel = self.gui.get_widget('panel_description_label')
  275.         
  276.         move = self._getCurrentMove()
  277.         if self.gameResult is not None:
  278.             (title, description) = self.gameResult
  279.             titleLabel.set_markup('<big><b>%s</b></big>' % title)
  280.             descriptionLabel.set_markup('<i>%s</i>' % description)
  281.             showPanel = True
  282.  
  283.         editToggle = self.gui.get_widget('comment_edit_toggle')
  284.         if self.showComments:
  285.             # Show the comments
  286.             if move is None:
  287.                 titleLabel.set_markup('<big><b>%s</b></big>' % _('Game Start'))
  288.             else:
  289.                 titleLabel.set_markup('<big><b>%s</b></big>' % self.generateMoveString(move))
  290.             
  291.             # Translators: Comment text when move has no comment
  292.             comment = _('No comment')
  293.             if move is not None and len(move.comment) > 0:
  294.                 comment = move.comment
  295.  
  296.             descriptionLabel.set_markup('<i>%s</i>' % comment)
  297.             editToggle.show()
  298.             showPanel = True
  299.         else:
  300.             editToggle.hide()
  301.  
  302.         if showPanel:
  303.             panel.show()
  304.         else:
  305.             panel.hide()
  306.  
  307.     def setShowComments(self, showComments):
  308.         """Enable comments on this view.
  309.         
  310.         'showComments' is true when move comments are visible.
  311.         """
  312.         self.showComments = showComments
  313.         self.updateInfoPanel()
  314.  
  315.     def setMoveFormat(self, format):
  316.         """Set the format to display the moves in.
  317.         
  318.         'format' is the format name to use (e.g. 'human', 'san'. Defaults to 'human')
  319.         """
  320.         self.moveFormat = format
  321.         
  322.         # Update the move list
  323.         iter = self.moveModel.get_iter_first()
  324.         while iter is not None:
  325.             move = self.moveModel.get_value(iter, 0)
  326.             if move is not None:
  327.                 string = self.generateMoveString(move)
  328.                 self.moveModel.set(iter, 2, string)
  329.             iter = self.moveModel.iter_next(iter)
  330.     
  331.     # Extended methods
  332.     
  333.     def setTitle(self, title):
  334.         """Extends glchess.ui.ViewController"""
  335.         self.title = title
  336.         if self.ui.view is self:
  337.             self.ui.updateTitle()
  338.  
  339.     def setNeedsSaving(self, needsSaving):
  340.         """Extends glchess.ui.ViewController"""
  341.         if self.needsSaving == needsSaving:
  342.             return
  343.         self.needsSaving = needsSaving
  344.         if self.ui.view is self:
  345.             self.ui.updateTitle()
  346.  
  347.     def render(self):
  348.         """Extends glchess.ui.ViewController"""
  349.         self.viewWidget.redraw()
  350.         
  351.     def setWhiteTime(self, total, current):
  352.         """Extends glchess.ui.ViewController"""
  353.         self.whiteTime = (total, current)
  354.         if self.ui.view is self:
  355.             self.ui.setTimers(self.whiteTime, self.blackTime)
  356.  
  357.     def setBlackTime(self, total, current):
  358.         """Extends glchess.ui.ViewController"""
  359.         self.blackTime = (total, current)
  360.         if self.ui.view is self:
  361.             self.ui.setTimers(self.whiteTime, self.blackTime)
  362.             
  363.     def setHasFile(self, hasFile):
  364.         """Extends glchess.ui.ViewController"""
  365.         self.hasFile = hasFile
  366.         self.ui._updateViewButtons()
  367.             
  368.     def setAttention(self, requiresAttention):
  369.         """Extends glchess.ui.ViewController"""
  370.         if self.requireAttention == requiresAttention:
  371.             return
  372.         self.requireAttention = requiresAttention
  373.         if requiresAttention:
  374.             self.ui._incAttentionCounter(1)
  375.         else:
  376.             self.ui._incAttentionCounter(-1)
  377.     
  378.     def generateMoveString(self, move):
  379.         """
  380.         """
  381.         moveNumber = (move.number - 1) / 2 + 1
  382.         WHITE  = glchess.chess.board.WHITE
  383.         BLACK  = glchess.chess.board.BLACK
  384.         colour = {0: BLACK, 1: WHITE}[move.number % 2]
  385.         
  386.         # Note SAN format is intentionally not translated
  387.         if self.moveFormat == 'san':
  388.             if move.number % 2 == 0:
  389.                 format = '%(movenum)2i. ... %(move_san)s'
  390.             else:
  391.                 format = '%(movenum)2i. %(move_san)s'
  392.             return format % {'movenum': moveNumber, 'move_san': glchess.chess.translate_notation(move.sanMove)}
  393.         
  394.         # Note FAN format is intentionally not translated
  395.         if self.moveFormat == 'fan':
  396.             if move.number % 2 == 0:
  397.                 format = '%(movenum)2i. ... %(move_san)s'
  398.             else:
  399.                 format = '%(movenum)2i. %(move_san)s'
  400.             return format % {'movenum': moveNumber, 'move_san': glchess.chess.translate_figurine_notation(colour, move.sanMove)}
  401.  
  402.         # Note LAN format is intentionally not translated
  403.         if self.moveFormat == 'lan':
  404.             if move.number % 2 == 0:
  405.                 format = '%(movenum)2i. ... %(move_can)s'
  406.             else:
  407.                 format = '%(movenum)2i. %(move_can)s'
  408.             return format % {'movenum': moveNumber, 'move_can': glchess.chess.translate_notation(move.canMove)}
  409.  
  410.         PAWN   = glchess.chess.board.PAWN
  411.         ROOK   = glchess.chess.board.ROOK
  412.         KNIGHT = glchess.chess.board.KNIGHT
  413.         BISHOP = glchess.chess.board.BISHOP
  414.         QUEEN  = glchess.chess.board.QUEEN
  415.         KING   = glchess.chess.board.KING
  416.  
  417.         if move.sanMove.startswith('O-O-O'):
  418.                            # Translators: Human Move String: Description of the white player making a long castle
  419.             description = {WHITE: _('White castles long'),
  420.                            # Translators: Human Move String: Description of the black player making a long castle
  421.                            BLACK: _('Black castles long')}[colour]
  422.         elif move.sanMove.startswith('O-O'):
  423.                            # Translators: Human Move String: Description of the white player making a short castle
  424.             description = {WHITE: _('White castles short'),
  425.                            # Translators: Human Move String: Description of the black player making a short castle
  426.                            BLACK: _('Black castles short')}[colour]
  427.         else:
  428.             # Note there are no move formats for pieces taking kings and this is not allowed in Chess rules
  429.                             # Translators: Human Move String: Description of a white pawn moving from %(start)s to %(end)s, e.g. 'c2 to c4'
  430.             descriptions = {(WHITE, PAWN,   None):   _('White pawn moves from %(start)s to %(end)s'),
  431.                             (WHITE, PAWN,   PAWN):   _('White pawn at %(start)s takes the black pawn at %(end)s'),
  432.                             (WHITE, PAWN,   ROOK):   _('White pawn at %(start)s takes the black rook at %(end)s'),
  433.                             (WHITE, PAWN,   KNIGHT): _('White pawn at %(start)s takes the black knight at %(end)s'),
  434.                             (WHITE, PAWN,   BISHOP): _('White pawn at %(start)s takes the black bishop at %(end)s'),
  435.                             (WHITE, PAWN,   QUEEN):  _('White pawn at %(start)s takes the black queen at %(end)s'),
  436.                             # Translators: Human Move String: Description of a white rook moving from %(start)s to %(end)s, e.g. 'a1 to a5'
  437.                             (WHITE, ROOK,   None):   _('White rook moves from %(start)s to %(end)s'),
  438.                             (WHITE, ROOK,   PAWN):   _('White rook at %(start)s takes the black pawn at %(end)s'),
  439.                             (WHITE, ROOK,   ROOK):   _('White rook at %(start)s takes the black rook at %(end)s'),
  440.                             (WHITE, ROOK,   KNIGHT): _('White rook at %(start)s takes the black knight at %(end)s'),
  441.                             (WHITE, ROOK,   BISHOP): _('White rook at %(start)s takes the black bishop at %(end)s'),
  442.                             (WHITE, ROOK,   QUEEN):  _('White rook at %(start)s takes the black queen at %(end)s'),
  443.                             # Translators: Human Move String: Description of a white knight moving from %(start)s to %(end)s, e.g. 'b1 to c3'
  444.                             (WHITE, KNIGHT, None):   _('White knight moves from %(start)s to %(end)s'),
  445.                             (WHITE, KNIGHT, PAWN):   _('White knight at %(start)s takes the black pawn at %(end)s'),
  446.                             (WHITE, KNIGHT, ROOK):   _('White knight at %(start)s takes the black rook at %(end)s'),
  447.                             (WHITE, KNIGHT, KNIGHT): _('White knight at %(start)s takes the black knight at %(end)s'),
  448.                             (WHITE, KNIGHT, BISHOP): _('White knight at %(start)s takes the black bishop at %(end)s'),
  449.                             (WHITE, KNIGHT, QUEEN):  _('White knight at %(start)s takes the black queen at %(end)s'),
  450.                             # Translators: Human Move String: Description of a white bishop moving from %(start)s to %(end)s, e.g. 'f1 to b5'
  451.                             (WHITE, BISHOP, None):   _('White bishop moves from %(start)s to %(end)s'),
  452.                             (WHITE, BISHOP, PAWN):   _('White bishop at %(start)s takes the black pawn at %(end)s'),
  453.                             (WHITE, BISHOP, ROOK):   _('White bishop at %(start)s takes the black rook at %(end)s'),
  454.                             (WHITE, BISHOP, KNIGHT): _('White bishop at %(start)s takes the black knight at %(end)s'),
  455.                             (WHITE, BISHOP, BISHOP): _('White bishop at %(start)s takes the black bishop at %(end)s'),
  456.                             (WHITE, BISHOP, QUEEN):  _('White bishop at %(start)s takes the black queen at %(end)s'),
  457.                             # Translators: Human Move String: Description of a white queen moving from %(start)s to %(end)s, e.g. 'd1 to d4'
  458.                             (WHITE, QUEEN,  None):   _('White queen moves from %(start)s to %(end)s'),
  459.                             (WHITE, QUEEN,  PAWN):   _('White queen at %(start)s takes the black pawn at %(end)s'),
  460.                             (WHITE, QUEEN,  ROOK):   _('White queen at %(start)s takes the black rook at %(end)s'),
  461.                             (WHITE, QUEEN,  KNIGHT): _('White queen at %(start)s takes the black knight at %(end)s'),
  462.                             (WHITE, QUEEN,  BISHOP): _('White queen at %(start)s takes the black bishop at %(end)s'),
  463.                             (WHITE, QUEEN,  QUEEN):  _('White queen at %(start)s takes the black queen at %(end)s'),
  464.                             # Translators: Human Move String: Description of a white king moving from %(start)s to %(end)s, e.g. 'e1 to f1'
  465.                             (WHITE, KING,   None):   _('White king moves from %(start)s to %(end)s'),
  466.                             (WHITE, KING,   PAWN):   _('White king at %(start)s takes the black pawn at %(end)s'),
  467.                             (WHITE, KING,   ROOK):   _('White king at %(start)s takes the black rook at %(end)s'),
  468.                             (WHITE, KING,   KNIGHT): _('White king at %(start)s takes the black knight at %(end)s'),
  469.                             (WHITE, KING,   BISHOP): _('White king at %(start)s takes the black bishop at %(end)s'),
  470.                             (WHITE, KING,   QUEEN):  _('White king at %(start)s takes the black queen at %(end)s'),
  471.                             # Translators: Human Move String: Description of a black pawn moving from %(start)s to %(end)s, e.g. 'c8 to c6'
  472.                             (BLACK, PAWN,   None):   _('Black pawn moves from %(start)s to %(end)s'),
  473.                             (BLACK, PAWN,   PAWN):   _('Black pawn at %(start)s takes the white pawn at %(end)s'),
  474.                             (BLACK, PAWN,   ROOK):   _('Black pawn at %(start)s takes the white rook at %(end)s'),
  475.                             (BLACK, PAWN,   KNIGHT): _('Black pawn at %(start)s takes the white knight at %(end)s'),
  476.                             (BLACK, PAWN,   BISHOP): _('Black pawn at %(start)s takes the white bishop at %(end)s'),
  477.                             (BLACK, PAWN,   QUEEN):  _('Black pawn at %(start)s takes the white queen at %(end)s'),
  478.                             # Translators: Human Move String: Description of a black rook moving from %(start)s to %(end)s, e.g. 'a8 to a4'
  479.                             (BLACK, ROOK,   None):   _('Black rook moves from %(start)s to %(end)s'),
  480.                             (BLACK, ROOK,   PAWN):   _('Black rook at %(start)s takes the white pawn at %(end)s'),
  481.                             (BLACK, ROOK,   ROOK):   _('Black rook at %(start)s takes the white rook at %(end)s'),
  482.                             (BLACK, ROOK,   KNIGHT): _('Black rook at %(start)s takes the white knight at %(end)s'),
  483.                             (BLACK, ROOK,   BISHOP): _('Black rook at %(start)s takes the white bishop at %(end)s'),
  484.                             (BLACK, ROOK,   QUEEN):  _('Black rook at %(start)s takes the white queen at %(end)s'),
  485.                             # Translators: Human Move String: Description of a black knight moving from %(start)s to %(end)s, e.g. 'b8 to c6'
  486.                             (BLACK, KNIGHT, None):   _('Black knight moves from %(start)s to %(end)s'),
  487.                             (BLACK, KNIGHT, PAWN):   _('Black knight at %(start)s takes the white pawn at %(end)s'),
  488.                             (BLACK, KNIGHT, ROOK):   _('Black knight at %(start)s takes the white rook at %(end)s'),
  489.                             (BLACK, KNIGHT, KNIGHT): _('Black knight at %(start)s takes the white knight at %(end)s'),
  490.                             (BLACK, KNIGHT, BISHOP): _('Black knight at %(start)s takes the white bishop at %(end)s'),
  491.                             (BLACK, KNIGHT, QUEEN):  _('Black knight at %(start)s takes the white queen at %(end)s'),
  492.                             # Translators: Human Move String: Description of a black bishop moving from %(start)s to %(end)s, e.g. 'f8 to b3'
  493.                             (BLACK, BISHOP, None):   _('Black bishop moves from %(start)s to %(end)s'),
  494.                             (BLACK, BISHOP, PAWN):   _('Black bishop at %(start)s takes the white pawn at %(end)s'),
  495.                             (BLACK, BISHOP, ROOK):   _('Black bishop at %(start)s takes the white rook at %(end)s'),
  496.                             (BLACK, BISHOP, KNIGHT): _('Black bishop at %(start)s takes the white knight at %(end)s'),
  497.                             (BLACK, BISHOP, BISHOP): _('Black bishop at %(start)s takes the white bishop at %(end)s'),
  498.                             (BLACK, BISHOP, QUEEN):  _('Black bishop at %(start)s takes the white queen at %(end)s'),
  499.                             # Translators: Human Move String: Description of a black queen moving from %(start)s to %(end)s, e.g. 'd8 to d5'
  500.                             (BLACK, QUEEN,  None):   _('Black queen moves from %(start)s to %(end)s'),
  501.                             (BLACK, QUEEN,  PAWN):   _('Black queen at %(start)s takes the white pawn at %(end)s'),
  502.                             (BLACK, QUEEN,  ROOK):   _('Black queen at %(start)s takes the white rook at %(end)s'),
  503.                             (BLACK, QUEEN,  KNIGHT): _('Black queen at %(start)s takes the white knight at %(end)s'),
  504.                             (BLACK, QUEEN,  BISHOP): _('Black queen at %(start)s takes the white bishop at %(end)s'),
  505.                             (BLACK, QUEEN,  QUEEN):  _('Black queen at %(start)s takes the white queen at %(end)s'),
  506.                             # Translators: Human Move String: Description of a black king moving from %(start)s to %(end)s, e.g. 'e8 to f8'
  507.                             (BLACK, KING,   None):   _('Black king moves from %(start)s to %(end)s'),
  508.                             (BLACK, KING,   PAWN):   _('Black king at %(start)s takes the white pawn at %(end)s'),
  509.                             (BLACK, KING,   ROOK):   _('Black king at %(start)s takes the white rook at %(end)s'),
  510.                             (BLACK, KING,   KNIGHT): _('Black king at %(start)s takes the white knight at %(end)s'),
  511.                             (BLACK, KING,   BISHOP): _('Black king at %(start)s takes the white bishop at %(end)s'),
  512.                             (BLACK, KING,   QUEEN):  _('Black king at %(start)s takes the white queen at %(end)s')}
  513.  
  514.             pieceType = move.piece.getType()                            
  515.             if move.victim is not None:
  516.                 victimType = move.victim.getType()
  517.             else:
  518.                 victimType = None
  519.             start = glchess.chess.translate_coordinate(move.start)
  520.             end = glchess.chess.translate_coordinate(move.end)            
  521.  
  522.             description = descriptions[colour, pieceType, victimType] % {'start': start, 'end': end}
  523.  
  524.         CHECK     = 'CHECK'
  525.         CHECKMATE = 'CHECKMATE'
  526.         STALEMATE = 'STALEMATE'
  527.         status = None
  528.         if move.opponentInCheck:
  529.             if move.opponentCanMove:
  530.                 status = CHECK
  531.             else:
  532.                 status = CHECKMATE
  533.         elif not move.opponentCanMove:
  534.             status = STALEMATE
  535.  
  536.                         # Translators: Human Move String: White player has made move %(description) and the opponent is in check
  537.         formatString = {(WHITE, CHECK):      _('%(movenum)2iw. %(description)s (Check)'),
  538.                         # Translators: Human Move String: White player has made move %(description) and the opponent is in checkmate
  539.                         (WHITE, CHECKMATE):  _('%(movenum)2iw. %(description)s (Checkmate)'),        
  540.                         # Translators: Human Move String: White player has made move %(description) and the opponent is in stalemate
  541.                         (WHITE, STALEMATE):  _('%(movenum)2iw. %(description)s (Stalemate)'),
  542.                         # Translators: Human Move String: White player has made move %(description) and the opponent is not in check or mate        
  543.                         (WHITE, None):       _('%(movenum)2iw. %(description)s'),
  544.                         # Translators: Human Move String: Black player has made move %(description) and the opponent is in check
  545.                         (BLACK, CHECK):      _('%(movenum)2ib. %(description)s (Check)'),
  546.                         # Translators: Human Move String: Black player has made move %(description) and the opponent is in checkmate
  547.                         (BLACK, CHECKMATE):  _('%(movenum)2ib. %(description)s (Checkmate)'),
  548.                         # Translators: Human Move String: Black player has made move %(description) and the opponent is in stalemate
  549.                         (BLACK, STALEMATE):  _('%(movenum)2ib. %(description)s (Stalemate)'),
  550.                         # Translators: Human Move String: Black player has made move %(description) and the opponent is not in check or mate
  551.                         (BLACK, None):       _('%(movenum)2ib. %(description)s')}[colour, status]
  552.  
  553.         # FIXME: Promotion
  554.  
  555.         return formatString % {'movenum': moveNumber, 'description': description}
  556.  
  557.     def addMove(self, move):
  558.         """Extends glchess.ui.ViewController"""        
  559.         # FIXME: Make a '@ui' player who watches for these itself?
  560.         iter = self.moveModel.append()
  561.         string = self.generateMoveString(move)
  562.         self.moveModel.set(iter, 0, move, 1, move.number, 2, string)
  563.  
  564.         # If is the current view and tracking the game select this
  565.         if self.selectedMove == -1:
  566.             # If editing comments don't stay on current move
  567.             if self.editingComment:
  568.                 self._setMoveNumber(move.number - 1)
  569.                 return
  570.             
  571.             self.ui._updateViewButtons()
  572.  
  573.     def undoMove(self):
  574.         """Extends glchess.ui.ViewController"""
  575.         iter = self.moveModel.iter_nth_child(None, len(self.moveModel) - 1)
  576.         self.moveModel.remove(iter)
  577.         self.selectedMove = -1
  578.         self.ui._updateViewButtons()        
  579.  
  580.     def endGame(self, game):
  581.         # Translators: Message displayed when a player wins. The %s is substituted with the winning player's name
  582.         format = _('%s wins')
  583.         
  584.         # If game completed show this in the GUI
  585.         if game.result is glchess.game.RESULT_WHITE_WINS:
  586.             title = format % game.getWhite().getName()
  587.         elif game.result is glchess.game.RESULT_BLACK_WINS:
  588.             title = format % game.getBlack().getName()
  589.         else:
  590.             # Translators: Message displayed when a game is drawn
  591.             title = _('Game is drawn')
  592.  
  593.         description = ''
  594.         if game.rule is glchess.game.RULE_CHECKMATE:
  595.             # Translators: Message displayed when the game ends due to a player being checkmated
  596.             description = _('Opponent is in check and cannot move (checkmate)')
  597.         elif game.rule is glchess.game.RULE_STALEMATE:
  598.             # Translators: Message displayed when the game terminates due to a stalemate
  599.             description = _('Opponent cannot move (stalemate)')
  600.         elif game.rule is glchess.game.RULE_FIFTY_MOVES:
  601.             # Translators: Message displayed when the game is drawn due to the fifty move rule
  602.             description = _('No piece has been taken or pawn moved in the last fifty moves')
  603.         elif game.rule is glchess.game.RULE_TIMEOUT:
  604.             # Translators: Message displayed when the game ends due to one player's clock stopping
  605.             description = _('Opponent has run out of time')
  606.         elif game.rule is glchess.game.RULE_THREE_FOLD_REPETITION:
  607.             # Translators: Message displayed when the game is drawn due to the three-fold-repitition rule
  608.             description = _('The same board state has occured three times (three fold repetition)')
  609.         elif game.rule is glchess.game.RULE_INSUFFICIENT_MATERIAL:
  610.             # Translators: Message displayed when the game is drawn due to the insufficient material rule
  611.             description = _('Neither player can cause checkmate (insufficient material)')
  612.         elif game.rule is glchess.game.RULE_RESIGN:
  613.             if game.result is glchess.game.RESULT_WHITE_WINS:
  614.                 # Translators: Message displayed when the game ends due to the black player resigning
  615.                 description = _('The black player has resigned')
  616.             elif game.result is glchess.game.RESULT_BLACK_WINS:
  617.                 # Translators: Message displayed when the game ends due to the white player resigning
  618.                 description = _('The white player has resigned')
  619.             else:
  620.                 assert(False)
  621.         elif game.rule is glchess.game.RULE_ABANDONMENT:
  622.             # Translators: Message displayed when a game is abandoned
  623.             description = _('The game has been abandoned')                
  624.         elif game.rule is glchess.game.RULE_DEATH:
  625.             # Translators: Message displayed when the game ends due to a player dying
  626.             description = _('One of the players has died')
  627.  
  628.         self.gameResult = (title, description)
  629.         self.updateInfoPanel()
  630.         self.ui._updateViewButtons()
  631.     
  632.     def close(self):
  633.         """Extends glchess.ui.ViewController"""
  634.         if self.requireAttention:
  635.             self.ui._incAttentionCounter(-1)
  636.         self.ui._removeView(self)
  637.     
  638.     # Public methods
  639.     
  640.     def _getCurrentMove(self):
  641.         if self.selectedMove == -1:
  642.             iter = self.moveModel.get_iter_from_string(str(len(self.moveModel) - 1))
  643.         else:
  644.             iter = self.moveModel.get_iter_from_string(str(self.selectedMove))
  645.         return self.moveModel.get_value(iter, 0)
  646.  
  647.     def _getModel(self):
  648.         """
  649.         """
  650.         return (self.moveModel, self.selectedMove)
  651.  
  652.     def _setMoveNumber(self, moveNumber):
  653.         """Set the move number this view requests.
  654.         
  655.         'moveNumber' is the move number to use (integer).
  656.         """
  657.         self.selectedMove = moveNumber
  658.         if self.feedback is not None:
  659.             self.feedback.setMoveNumber(moveNumber)
  660.         self.updateInfoPanel()
  661.  
  662.     # Callbacks
  663.     
  664.     def _on_comment_edit_button_toggled(self, widget):
  665.         """Gtk+ callback"""
  666.         label = self.gui.get_widget('panel_description_label')
  667.         entry = self.gui.get_widget('comment_text')
  668.         buffer = entry.get_buffer()
  669.         
  670.         move = self._getCurrentMove()
  671.         
  672.         # FIXME
  673.         if move is None:
  674.             return
  675.         
  676.         self.editingComment = widget.get_active()
  677.         if self.editingComment:
  678.             buffer.set_text(move.comment)
  679.             entry.grab_focus()
  680.             page = 1
  681.         else:
  682.             comment = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
  683.             
  684.             # FIXME: This should be notified so the game is considered modified for saving purposes
  685.             # self.feedback.setComment(move, comment)
  686.  
  687.             move.comment = comment
  688.             label.set_text(comment)
  689.             buffer.set_text('')
  690.             page = 0
  691.         self.gui.get_widget('comment_notebook').set_current_page(page)
  692.